﻿using System;
using System.Threading;

namespace DambachMulti.Processing
{
	public class Tuner
	{
		/// <summary>
		/// Returns the number of arbitrary sequential operations this processor can perform in a millisecond
		/// </summary>
		/// <returns>Number of floating point operations completed.</returns>
		public static long ComputeSequentialThroughput( int milliseconds )
		{
			Boolean keepProcessing = true;
			Timer t = new Timer( delegate {			//create a timer to stop our throughput calculating loop				
				keepProcessing = false;				//change the sentinel value to stop computation of stuff								
			}, null, milliseconds, -1 ); //null=don't pass a state, don't re-signal for periodic scheduling

			Random r = new Random( 36 );	//initialize our randomizer with the perfect number...36 Sum(1, 36) == 666, I am from a different solar system...
			long operations = 1;			//count the initialization of the variable as a single operation... Muhahahahahahahah

			//since we are only reading it, I suppose no locking is necessary...
			double floatTmp; //only use inside, declared here to reduce garbage collection during the massive number of iterations
			while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{
				operations++; //below line is not used but we are measuring how many operations are possible in a span of time
				floatTmp = (1 + r.NextDouble()) / (1 + r.NextDouble()); //just holds the result
			}	//division is a good floating point operation to measure, and random number generation might not be though...			
			t.Dispose();
			return operations;
		}

		/// <summary>
		/// Method of calculating general floating point multi-core throughput, without contention over shared resources...that must be simulated in the user defined metric methods
		/// </summary>
		public static long ComputeMultiCoreThroughput( int milliseconds, int processorCount )
		{
			Boolean				keepProcessing	= true;			//keep processing until the timer elapses			
			ManualResetEvent	mre				= new ManualResetEvent(false); //a meal thats not quite ready to eat.
			Timer t = new Timer( delegate {	//don't do anything with this object								
				keepProcessing = false;		//change the sentinel value to stop computation of stuff
				mre.Set();
			}, null, milliseconds, -1 );	//null=don't pass a state, don't re-signal for periodic scheduling

			Random	r			= new Random( 36 );			//initialize our randomizer with the perfect number...36 Sum(1, 36) == 666, I am from a different solar system...
			long	operations	= 1;						//count the initialization of the variable as a single operation... Muhahahahahahahah			

			//since we are only reading it, I suppose no locking is necessary...Tested with many trials, no problems reading variable
			if( processorCount == Environment.ProcessorCount )
				processorCount--; //tis a value type, so only our local copy will be modified
			for( int i = 0; i < processorCount; i++ ) //the minus one is necessary to keep this thread from being starved
			{
				ThreadPool.QueueUserWorkItem( delegate { //each work item, works as long as the threadpool
					while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
					{
						double floatTmp = (1 + r.NextDouble()) / (1 + r.NextDouble()); //just holds the result
						Interlocked.Increment( ref operations ); //below line is not used but we are measuring how many operations are possible in a span of time
					}
				} ); //this method is going to do much worse
			}	//division is a good floating point operation to measure, and random number generation might not be though...						

			//we do this because if numCores == ThreadPoolItems, then this thread's timer is not moved forward as it should be and the result is innacurate
			while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{
				double floatTmp = (1 + r.NextDouble()) / (1 + r.NextDouble()); //just holds the result
				Interlocked.Increment( ref operations ); //below line is not used but we are measuring how many operations are possible in a span of time
			}
			mre.WaitOne();			
			t.Dispose();
			return operations;
		}

		/// <summary>
		/// Oversimplified way of looking at the hit we take for being multi-threaded
		/// </summary>		
		public static long ComputeMultiPooledThroughput( int milliseconds )
		{
			Boolean keepProcessing = true;
			Timer t = new Timer( delegate {	//don't do anything with this object								
				keepProcessing = false;		//change the sentinel value to stop computation of stuff
			}, null, milliseconds, -1 );	//null=don't pass a state, don't re-signal for periodic scheduling

			Random r = new Random( 36 );	//initialize our randomizer with the perfect number...36 Sum(1, 36) == 666, I am from a different solar system...
			long operations = 1;			//count the initialization of the variable as a single operation... Muhahahahahahahah

			//since we are only reading it, I suppose no locking is necessary...Tested with many trials, no problems reading variable
			while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{	
				ThreadPool.QueueUserWorkItem( delegate {
					double floatTmp = (1 + r.NextDouble()) / (1 + r.NextDouble()); //just holds the result                          		
					Interlocked.Increment( ref operations ); //below line is not used but we are measuring how many operations are possible in a span of time
				} ); //this method is going to do much worse
			}	//division is a good floating point operation to measure, and random number generation might not be though...			
			t.Dispose();
			return operations;
		}

		//Now for methods that deal with a computation supplied by the user		

		/// <summary>
		/// Returns the number of arbitrary sequential operations this processor can perform in a millisecond
		/// </summary>
		/// <returns>Number of floating point operations completed.</returns>
		public static long ComputeSequentialThroughput( ThreadStart method, int milliseconds )
		{
			Boolean keepProcessing = true;
			Timer t = new Timer( delegate {			//create a timer to stop our throughput calculating loop
				keepProcessing = false;				//change the sentinel value to stop computation of stuff								
			}, null, milliseconds, -1 ); //null=don't pass a state, don't re-signal for periodic scheduling

			long operations = 1;			//count the initialization of the variable as a single operation... Muhahahahahahahah

			//since we are only reading it, I suppose no locking is necessary...			
			while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{
				operations++; //below line is not used but we are measuring how many operations are possible in a span of time
				method();
			}
			t.Dispose();
			return operations;
		}

		/// <summary>
		/// Method of calculating general floating point multi-core throughput
		/// </summary>
		public static long ComputeMultiCoreThroughput( ThreadStart method, int milliseconds, int processorCount )
		{
			Boolean				keepProcessing	= true;			//keep processing until the timer elapses			
			ManualResetEvent	mre				= new ManualResetEvent(false); //a meal thats not quite ready to eat.
			Timer t = new Timer( delegate {	//don't do anything with this object								
				keepProcessing = false;		//change the sentinel value to stop computation of stuff				
			}, null, milliseconds, -1 );	//null=don't pass a state, don't re-signal for periodic scheduling
			
			long operations = 1; //count the initialization of the variable as a single operation... Muhahahahahahahah			

			//since we are only reading it, I suppose no locking is necessary...Tested with many trials, no problems reading variable
			if( processorCount == Environment.ProcessorCount )
				processorCount--; //tis a value type, so only our local copy will be modified
			for( int i = 0; i < processorCount; i++ ) //the minus one is necessary to keep this thread from being starved
			{
				ThreadPool.QueueUserWorkItem( delegate { //each work item, works as long as the threadpool
					while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
					{
						method();									//madness...everything is madness...
						Interlocked.Increment( ref operations );	//below line is not used but we are measuring how many operations are possible in a span of time
					}
				} ); //this method is going to do much worse
			} //division is a good floating point operation to measure, and random number generation might not be though...

			//we do this because if numCores == ThreadPoolItems, then this thread's timer is not moved forward as it should be and the result is innacurate
			while( keepProcessing ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{
				method(); //execute the user supplied method
				Interlocked.Increment( ref operations ); //below line is not used but we are measuring how many operations are possible in a span of time
			}
			//mre.WaitOne();
			return operations;
		}

		/// <summary>
		/// Compute throughput of a user defined metric using the Multi-Pool model
		/// </summary>		
		public static long ComputeMultiPooledThroughput( ThreadStart method, int milliseconds )
		{
			Boolean				keepProcessing	= true;
			ManualResetEvent	mre				= new ManualResetEvent(false); //a meal thats not quite ready to eat.
			Timer t = new Timer( delegate {	//don't do anything with this object								
				keepProcessing = false;		//change the sentinel value to stop computation of stuff				
				mre.Set();
			}, null, milliseconds, -1 );	//null=don't pass a state, don't re-signal for periodic scheduling
			
			long operations = 1;			//count the initialization of the variable as a single operation... Muhahahahahahahah

			//since we are only reading it, I suppose no locking is necessary...Tested with many trials, no problems reading variable
			while( mre.WaitOne(1, false) == false ) //hmmm, race condition seems highly unlikely with a boolean involved...either it is, or it isn't... right???
			{
				ThreadPool.QueueUserWorkItem( delegate {
					method();
					Interlocked.Increment( ref operations ); //below line is not used but we are measuring how many operations are possible in a span of time
				} ); //this method is going to do much worse
			}	//division is a good floating point operation to measure, and random number generation might not be though...			
			t.Dispose(); //wierdness			
			mre.WaitOne();
			return operations;
		}
	}
}

